--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Node / ReticulumProjects / MeshChatX.git / files / meshchatx / src / backend / http / routes / favourites.py
Displaying Raw • Download
meshchatx/src/backend/http/routes/favourites.py master (b89dc9e6) Text, 16.20 KB
T8b949e# SPDX-License-Identifier: 0BSD
T8b949e"""HTTP routes: favourites."""
Tff7b72from T7ee787__future__ Tff7b72import Te6edf3annotations
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787httpT7ee787.T7ee787meshchat_names Tff7b72import Tb4b4b4( T8b949e# noqa: F401
Te6edf3GeoValidationErrorTb4b4b4,
Te6edf3OutboundHttpBlockedErrorTb4b4b4,
Te6edf3OverlayExportErrorTb4b4b4,
Te6edf3OverlaySourceParseErrorTb4b4b4,
Te6edf3PluginSecurityErrorTb4b4b4,
Te6edf3AsyncUtilsTb4b4b4,
Te6edf3InterfaceConfigParserTb4b4b4,
Te6edf3InterfaceDiscoveryTb4b4b4,
Te6edf3InterfaceEditorTb4b4b4,
Te6edf3LOGIN_PATHTb4b4b4,
Te6edf3LXMFTb4b4b4,
Te6edf3LxmfAudioFieldTb4b4b4,
Te6edf3LxmfFileAttachmentTb4b4b4,
Te6edf3LxmfFileAttachmentsFieldTb4b4b4,
Te6edf3LxmfImageFieldTb4b4b4,
Te6edf3MAX_EXPORT_TILESTb4b4b4,
Te6edf3MarkdownRendererTb4b4b4,
Te6edf3NomadnetFileDownloaderTb4b4b4,
Te6edf3NomadnetPageDownloaderTb4b4b4,
Te6edf3RNProbeHandlerTb4b4b4,
Te6edf3RNSTb4b4b4,
Te6edf3ReticulumMeshChatTb4b4b4,
Te6edf3SETUP_PATHTb4b4b4,
Te6edf3TRANSPARENT_TILETb4b4b4,
Te6edf3TelemeterTb4b4b4,
Te6edf3UTCTb4b4b4,
Te6edf3WSMsgTypeTb4b4b4,
Te6edf3_is_chaquopy_androidTb4b4b4,
Te6edf3_is_loopback_bind_hostTb4b4b4,
Te6edf3_request_client_ipTb4b4b4,
Te6edf3aiohttpTb4b4b4,
Te6edf3app_versionTb4b4b4,
Te6edf3assert_migration_context_pathsTb4b4b4,
Te6edf3asyncioTb4b4b4,
Te6edf3base64Tb4b4b4,
Te6edf3bcryptTb4b4b4,
Te6edf3binasciiTb4b4b4,
Te6edf3build_blocklist_export_documentTb4b4b4,
Te6edf3build_export_documentTb4b4b4,
Te6edf3build_messages_export_bundleTb4b4b4,
Te6edf3cache_statsTb4b4b4,
Te6edf3cancel_inbound_deliveriesTb4b4b4,
Te6edf3castTb4b4b4,
Te6edf3compute_lxmf_conversation_unread_from_latest_rowTb4b4b4,
Te6edf3configparserTb4b4b4,
Te6edf3contextlibTb4b4b4,
Te6edf3convert_db_favourite_to_dictTb4b4b4,
Te6edf3convert_db_lxmf_message_to_dictTb4b4b4,
Te6edf3convert_lxmf_message_to_dictTb4b4b4,
Te6edf3convert_nomadnet_field_data_to_mapTb4b4b4,
Te6edf3convert_nomadnet_string_data_to_mapTb4b4b4,
Te6edf3convert_propagation_node_state_to_stringTb4b4b4,
Te6edf3copyTb4b4b4,
Te6edf3datetimeTb4b4b4,
Te6edf3describe_port_conflictTb4b4b4,
Te6edf3detect_image_format_from_magicTb4b4b4,
Te6edf3ensure_outbound_http_allowedTb4b4b4,
Te6edf3ensure_session_csrf_tokenTb4b4b4,
Te6edf3filter_announced_dicts_by_search_queryTb4b4b4,
Te6edf3fresh_storage_at_targetTb4b4b4,
Te6edf3get_cached_active_linkTb4b4b4,
Te6edf3get_file_pathTb4b4b4,
Te6edf3get_sessionTb4b4b4,
Te6edf3get_trusted_proxy_cidrsTb4b4b4,
Te6edf3gif_utilsTb4b4b4,
Te6edf3i2p_supportTb4b4b4,
Te6edf3import_messages_export_bundleTb4b4b4,
Te6edf3ioTb4b4b4,
Te6edf3is_mbtiles_filenameTb4b4b4,
Te6edf3is_path_within_dirTb4b4b4,
Te6edf3is_port_in_useTb4b4b4,
Te6edf3is_user_facing_lxmf_payloadTb4b4b4,
Te6edf3jsonTb4b4b4,
Te6edf3list_host_network_interfacesTb4b4b4,
Te6edf3list_inbound_deliveriesTb4b4b4,
Te6edf3list_portsTb4b4b4,
Te6edf3load_app_security_settingsTb4b4b4,
Te6edf3loggerTb4b4b4,
Te6edf3loggingTb4b4b4,
Te6edf3lxmf_sidebar_preview_for_conversation_latest_rowTb4b4b4,
Te6edf3memory_log_handlerTb4b4b4,
Te6edf3message_fields_have_attachmentsTb4b4b4,
Te6edf3migrate_legacy_to_targetTb4b4b4,
Te6edf3mime_for_image_typeTb4b4b4,
Te6edf3normalize_identity_storage_hashTb4b4b4,
Te6edf3normalize_lxmf_sieve_filtersTb4b4b4,
Te6edf3normalize_message_blocklistTb4b4b4,
Te6edf3osTb4b4b4,
Te6edf3parse_bool_query_paramTb4b4b4,
Te6edf3parse_import_documentTb4b4b4,
Te6edf3parse_lxmf_display_nameTb4b4b4,
Te6edf3parse_lxmf_propagation_node_app_dataTb4b4b4,
Te6edf3parse_lxmf_sieve_filters_jsonTb4b4b4,
Te6edf3parse_lxmf_stamp_costTb4b4b4,
Te6edf3parse_message_blocklist_jsonTb4b4b4,
Te6edf3parse_nomadnetwork_node_display_nameTb4b4b4,
Te6edf3platformTb4b4b4,
Te6edf3privacy_mode_enabledTb4b4b4,
Te6edf3psutilTb4b4b4,
Te6edf3purge_messages_before_cutoffTb4b4b4,
Te6edf3reTb4b4b4,
Te6edf3resolve_message_age_cutoffTb4b4b4,
Te6edf3reticulum_pathfindingTb4b4b4,
Te6edf3rotate_session_csrf_tokenTb4b4b4,
Te6edf3rrc_protocolTb4b4b4,
Te6edf3safe_path_under_dirTb4b4b4,
Te6edf3sanitize_sticker_emojiTb4b4b4,
Te6edf3sanitize_sticker_nameTb4b4b4,
Te6edf3sanitize_websocket_config_updateTb4b4b4,
Te6edf3save_app_security_settingsTb4b4b4,
Te6edf3secretsTb4b4b4,
Te6edf3shutilTb4b4b4,
Te6edf3sqlite3Tb4b4b4,
Te6edf3sticker_pack_utilsTb4b4b4,
Te6edf3sysTb4b4b4,
Te6edf3tempfileTb4b4b4,
Te6edf3threadingTb4b4b4,
Te6edf3timeTb4b4b4,
Te6edf3tracebackTb4b4b4,
Te6edf3user_agent_hashTb4b4b4,
Te6edf3validate_export_documentTb4b4b4,
Te6edf3webTb4b4b4,
Te6edf3websocket_type_requires_authTb4b4b4,
Te6edf3zipfileTb4b4b4,
Tb4b4b4)
Tff7b72def Td2a8ffregister_favourites_routesTb4b4b4(Te6edf3routesTb4b4b4, Te6edf3appTb4b4b4)Tb4b4b4:
T8b949e# announce
Tf0883e@routesTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/announceTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffannounce_triggerTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
Tff7b72await Te6edf3appTff7b72.Td2a8ffannounceTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffannouncingTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# serve announces
T8b949e# serve announces
Tf0883e@routesTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/announcesTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffannounces_getTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get query params
Te6edf3aspect Tff7b72= Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffaspectTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3identity_hash Tff7b72= Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffidentity_hashTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3destination_hash Tff7b72= Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3search_query Tff7b72= Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffsearchTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3limit Tff7b72= Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fflimitTa5d6ff"Tb4b4b4)
Te6edf3limit Tff7b72= Tffa657intTb4b4b4(Te6edf3limitTb4b4b4) Tff7b72if Te6edf3limit Tff7b72is Tff7b72not Tff7b72None Tff7b72and Te6edf3limit Tff7b72!= Ta5d6ff"Ta5d6ff" Tff7b72else Tff7b72None
Tff7b72except Tf85149ValueErrorTb4b4b4:
Te6edf3limit Tff7b72= Tff7b72None
Tff7b72tryTb4b4b4:
Te6edf3offset Tff7b72= Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffoffsetTa5d6ff"Tb4b4b4)
Te6edf3offset Tff7b72= Tffa657intTb4b4b4(Te6edf3offsetTb4b4b4) Tff7b72if Te6edf3offset Tff7b72is Tff7b72not Tff7b72None Tff7b72else T79c0ff0
Tff7b72except Tf85149ValueErrorTb4b4b4:
Te6edf3offset Tff7b72= T79c0ff0
Tff7b72if Tff7b72not Te6edf3search_query Tff7b72and Te6edf3limit Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3limit Tff7b72= Te6edf3appTff7b72.Td2a8ff_default_announce_fetch_limitTb4b4b4(Te6edf3aspectTb4b4b4)
Te6edf3search_max Tff7b72= T79c0ff2000
Tff7b72if Te6edf3appTff7b72.Td2a8ffcurrent_context Tff7b72and Te6edf3appTff7b72.Td2a8ffcurrent_contextTff7b72.Td2a8ffconfigTb4b4b4:
Te6edf3sm Tff7b72= Te6edf3appTff7b72.Td2a8ffcurrent_contextTff7b72.Td2a8ffconfigTff7b72.Td2a8ffannounce_search_max_fetchTff7b72.Td2a8ffgetTb4b4b4(Tb4b4b4)
Tff7b72if Te6edf3sm Tff7b72is Tff7b72not Tff7b72None Tff7b72and Te6edf3sm Tff7b72> T79c0ff0Tb4b4b4:
Te6edf3search_max Tff7b72= Tffa657minTb4b4b4(Tffa657intTb4b4b4(Te6edf3smTb4b4b4)Tb4b4b4, T79c0ff10_000Tb4b4b4)
Te6edf3include_blocked Tff7b72= Tb4b4b4(
Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffinclude_blockedTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6fffalseTa5d6ff"Tb4b4b4)Tff7b72.Td2a8fflowerTb4b4b4(Tb4b4b4) Tff7b72== Ta5d6ff"Ta5d6fftrueTa5d6ff"
Tb4b4b4)
Te6edf3blocked_identity_hashes Tff7b72= Tff7b72None
Tff7b72if Tff7b72not Te6edf3include_blockedTb4b4b4:
Te6edf3blocked Tff7b72= Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffget_blocked_destinationsTb4b4b4,
Tb4b4b4)
Te6edf3blocked_identity_hashes Tff7b72= Tb4b4b4[Te6edf3bTb4b4b4[Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3b Tff7b72in Te6edf3blockedTb4b4b4]
Tff7b72if Te6edf3search_queryTb4b4b4:
T8b949e# limit here is the caller's desired page size for the
T8b949e# paginated, filtered results below, not the number of rows
T8b949e# to scan for matches. Always scan up to search_max rows so
T8b949e# matches outside the most-recent page are still found.
Te6edf3db_limit Tff7b72= Te6edf3search_max
Tff7b72elseTb4b4b4:
Te6edf3db_limit Tff7b72= Te6edf3limit
Te6edf3db_offset Tff7b72= Te6edf3offset Tff7b72if Tff7b72not Te6edf3search_query Tff7b72else T79c0ff0
Te6edf3results Tff7b72= Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(
Te6edf3appTff7b72.Td2a8ffannounce_managerTff7b72.Td2a8ffget_filtered_announcesTb4b4b4,
Te6edf3aspectTff7b72=Te6edf3aspectTb4b4b4,
Te6edf3identity_hashTff7b72=Te6edf3identity_hashTb4b4b4,
Te6edf3destination_hashTff7b72=Te6edf3destination_hashTb4b4b4,
Te6edf3queryTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3blocked_identity_hashesTff7b72=Te6edf3blocked_identity_hashesTb4b4b4,
Te6edf3limitTff7b72=Te6edf3db_limitTb4b4b4,
Te6edf3offsetTff7b72=Te6edf3db_offsetTb4b4b4,
Tb4b4b4)
Te6edf3total_count Tff7b72= T79c0ff0
Tff7b72if Tff7b72not Te6edf3search_queryTb4b4b4:
Tff7b72if Te6edf3db_limit Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3total_count Tff7b72= Tffa657lenTb4b4b4(Te6edf3resultsTb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3total_count Tff7b72= Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(
Te6edf3appTff7b72.Td2a8ffannounce_managerTff7b72.Td2a8ffget_filtered_announces_countTb4b4b4,
Te6edf3aspectTff7b72=Te6edf3aspectTb4b4b4,
Te6edf3identity_hashTff7b72=Te6edf3identity_hashTb4b4b4,
Te6edf3destination_hashTff7b72=Te6edf3destination_hashTb4b4b4,
Te6edf3queryTff7b72=Tff7b72NoneTb4b4b4,
Te6edf3blocked_identity_hashesTff7b72=Te6edf3blocked_identity_hashesTb4b4b4,
Tb4b4b4)
T8b949e# pre-fetch icons and other data to avoid N+1 queries in convert_db_announce_to_dict
Te6edf3all_announces Tff7b72= Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(
Te6edf3appTff7b72.Td2a8ff_batch_convert_announces_to_api_dictsTb4b4b4,
Te6edf3resultsTb4b4b4,
Te6edf3aspectTb4b4b4,
Tb4b4b4)
T8b949e# apply search query filter if provided
Tff7b72if Te6edf3search_queryTb4b4b4:
Te6edf3all_announces Tff7b72= Te6edf3filter_announced_dicts_by_search_queryTb4b4b4(
Te6edf3all_announcesTb4b4b4,
Te6edf3search_queryTb4b4b4,
Tb4b4b4)
T8b949e# Re-calculate total_count after search filter
Te6edf3total_count Tff7b72= Tffa657lenTb4b4b4(Te6edf3all_announcesTb4b4b4)
T8b949e# apply pagination after search
Te6edf3start Tff7b72= Te6edf3offset
Te6edf3end Tff7b72= Te6edf3start Tff7b72+ Tb4b4b4(Te6edf3limit Tff7b72if Te6edf3limit Tff7b72is Tff7b72not Tff7b72None Tff7b72else Te6edf3total_countTb4b4b4)
Te6edf3paginated_results Tff7b72= Te6edf3all_announcesTb4b4b4[Te6edf3startTb4b4b4:Te6edf3endTb4b4b4]
Tff7b72elseTb4b4b4:
T8b949e# We already paginated in DB, and total_count was calculated before processing
Te6edf3paginated_results Tff7b72= Te6edf3all_announces
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffannouncesTa5d6ff"Tb4b4b4: Te6edf3paginated_resultsTb4b4b4,
Ta5d6ff"Ta5d6fftotal_countTa5d6ff"Tb4b4b4: Te6edf3total_countTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tf0883e@routesTff7b72.Td2a8ffpostTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/announces/queryTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8ffannounces_queryTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3data Tff7b72= Tff7b72await Te6edf3requestTff7b72.Td2a8ffjsonTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Te6edf3data Tff7b72= Tb4b4b4{Tb4b4b4}
Te6edf3destination_hashes Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashesTa5d6ff"Tb4b4b4)
Te6edf3aspects Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffaspectsTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3destination_hashesTb4b4b4, Tffa657listTb4b4b4) Tff7b72or Tff7b72not Te6edf3destination_hashesTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffannouncesTa5d6ff"Tb4b4b4: Tb4b4b4[Tb4b4b4]Tb4b4b4, Ta5d6ff"Ta5d6fftotal_countTa5d6ff"Tb4b4b4: T79c0ff0Tb4b4b4}Tb4b4b4)
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3aspectsTb4b4b4, Tffa657listTb4b4b4) Tff7b72or Tff7b72not Te6edf3aspectsTb4b4b4:
Te6edf3aspects Tff7b72= Tb4b4b4[Ta5d6ff"Ta5d6fflxmf.deliveryTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ffnomadnetwork.nodeTa5d6ff"Tb4b4b4]
Te6edf3blocked_identity_hashes Tff7b72= Tff7b72None
Tff7b72if Te6edf3appTff7b72.Td2a8ffcurrent_context Tff7b72and Te6edf3appTff7b72.Td2a8ffcurrent_contextTff7b72.Td2a8ffconfigTb4b4b4:
Te6edf3blocked Tff7b72= Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffmiscTff7b72.Td2a8ffget_blocked_destinationsTb4b4b4,
Tb4b4b4)
Te6edf3blocked_identity_hashes Tff7b72= Tb4b4b4[Te6edf3bTb4b4b4[Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4] Tff7b72for Te6edf3b Tff7b72in Te6edf3blockedTb4b4b4]
Te6edf3results Tff7b72= Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(
Te6edf3appTff7b72.Td2a8ffannounce_managerTff7b72.Td2a8ffget_announces_for_destination_hashesTb4b4b4,
Te6edf3destination_hashesTff7b72=Te6edf3destination_hashesTb4b4b4,
Te6edf3aspectsTff7b72=Te6edf3aspectsTb4b4b4,
Te6edf3blocked_identity_hashesTff7b72=Te6edf3blocked_identity_hashesTb4b4b4,
Tb4b4b4)
Te6edf3all_announces Tff7b72= Tff7b72await Te6edf3asyncioTff7b72.Td2a8ffto_threadTb4b4b4(
Te6edf3appTff7b72.Td2a8ff_batch_convert_announces_to_api_dictsTb4b4b4,
Te6edf3resultsTb4b4b4,
Tff7b72NoneTb4b4b4,
Tff7b72FalseTb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffannouncesTa5d6ff"Tb4b4b4: Te6edf3all_announcesTb4b4b4,
Ta5d6ff"Ta5d6fftotal_countTa5d6ff"Tb4b4b4: Tffa657lenTb4b4b4(Te6edf3all_announcesTb4b4b4)Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# serve favourites
T8b949e# serve favourites
Tf0883e@routesTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/favouritesTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8fffavourites_getTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get query params
Te6edf3aspect Tff7b72= Te6edf3requestTff7b72.Td2a8ffqueryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffaspectTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
T8b949e# get favourites from database
Te6edf3results Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_favouritesTb4b4b4(Te6edf3aspectTff7b72=Te6edf3aspectTb4b4b4)
T8b949e# process favourites
Te6edf3favourites Tff7b72= Tb4b4b4[Te6edf3convert_db_favourite_to_dictTb4b4b4(Te6edf3favouriteTb4b4b4) Tff7b72for Te6edf3favourite Tff7b72in Te6edf3resultsTb4b4b4]
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6fffavouritesTa5d6ff"Tb4b4b4: Te6edf3favouritesTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# add favourite
T8b949e# add favourite
Tf0883e@routesTff7b72.Td2a8ffpostTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/favourites/addTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8fffavourites_addTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get request data
Te6edf3data Tff7b72= Tff7b72await Te6edf3requestTff7b72.Td2a8ffjsonTb4b4b4(Tb4b4b4)
Te6edf3destination_hash Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3display_name Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdisplay_nameTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
Te6edf3aspect Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffaspectTa5d6ff"Tb4b4b4, Tff7b72NoneTb4b4b4)
T8b949e# destination hash is required
Tff7b72if Te6edf3destination_hash Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffdestination_hash is requiredTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff422Tb4b4b4,
Tb4b4b4)
T8b949e# display name is required
Tff7b72if Te6edf3display_name Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffdisplay_name is requiredTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff422Tb4b4b4,
Tb4b4b4)
T8b949e# aspect is required
Tff7b72if Te6edf3aspect Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffaspect is requiredTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff422Tb4b4b4,
Tb4b4b4)
T8b949e# upsert favourite
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffupsert_favouriteTb4b4b4(
Te6edf3destination_hashTb4b4b4,
Te6edf3display_nameTb4b4b4,
Te6edf3aspectTb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffFavourite has been added!Ta5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# rename favourite
T8b949e# rename favourite
Tf0883e@routesTff7b72.Td2a8ffpostTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/favourites/Tffd700{destination_hash}Ta5d6ff/renameTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8fffavourites_renameTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get path params
Te6edf3destination_hash Tff7b72= Te6edf3requestTff7b72.Td2a8ffmatch_infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
T8b949e# get request data
Te6edf3data Tff7b72= Tff7b72await Te6edf3requestTff7b72.Td2a8ffjsonTb4b4b4(Tb4b4b4)
Te6edf3raw_name Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdisplay_nameTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3raw_name Tff7b72is Tff7b72NoneTb4b4b4:
Te6edf3display_name Tff7b72= Ta5d6ff"Ta5d6ff"
Tff7b72elif Tffa657isinstanceTb4b4b4(Te6edf3raw_nameTb4b4b4, Tffa657strTb4b4b4)Tb4b4b4:
Te6edf3display_name Tff7b72= Te6edf3raw_nameTff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Tff7b72elseTb4b4b4:
Te6edf3display_name Tff7b72= Tffa657strTb4b4b4(Te6edf3raw_nameTb4b4b4)Tff7b72.Td2a8ffstripTb4b4b4(Tb4b4b4)
Te6edf3favourite Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_favourite_by_destination_hashTb4b4b4(
Te6edf3destination_hashTb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3favourite Tff7b72is Tff7b72NoneTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffFavourite not foundTa5d6ff"Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff404Tb4b4b4,
Tb4b4b4)
T8b949e# update display name if provided
Tff7b72if Tffa657lenTb4b4b4(Te6edf3display_nameTb4b4b4) Tff7b72> T79c0ff0Tb4b4b4:
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffupsert_custom_display_nameTb4b4b4(
Te6edf3destination_hashTb4b4b4,
Te6edf3display_nameTb4b4b4,
Tb4b4b4)
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffupsert_favouriteTb4b4b4(
Te6edf3destination_hashTb4b4b4,
Te6edf3display_nameTb4b4b4,
Te6edf3favouriteTb4b4b4[Ta5d6ff"Ta5d6ffaspectTa5d6ff"Tb4b4b4]Tb4b4b4,
Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffFavourite has been renamedTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# delete favourite
T8b949e# delete favourite
Tf0883e@routesTff7b72.Td2a8ffdeleteTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/favourites/Tffd700{destination_hash}Ta5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8fffavourites_deleteTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
T8b949e# get path params
Te6edf3destination_hash Tff7b72= Te6edf3requestTff7b72.Td2a8ffmatch_infoTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
T8b949e# delete favourite
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffdelete_favouriteTb4b4b4(Te6edf3destination_hashTb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffFavourite has been deleted!Ta5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
T8b949e# bulk import favourites
T8b949e# bulk import favourites
Tf0883e@routesTff7b72.Td2a8ffpostTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/favourites/importTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8fffavourites_importTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
Tff7b72tryTb4b4b4:
Te6edf3data Tff7b72= Tff7b72await Te6edf3requestTff7b72.Td2a8ffjsonTb4b4b4(Tb4b4b4)
Te6edf3entries Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fffavouritesTa5d6ff"Tb4b4b4, Tb4b4b4[Tb4b4b4]Tb4b4b4)
Tff7b72if Tff7b72not Tffa657isinstanceTb4b4b4(Te6edf3entriesTb4b4b4, Tffa657listTb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffInvalid import format: favourites must be an arrayTa5d6ff"Tb4b4b4,
Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff400Tb4b4b4,
Tb4b4b4)
Te6edf3seen Tff7b72= Tb4b4b4{Tb4b4b4}
Te6edf3no_hash Tff7b72= Tb4b4b4[Tb4b4b4]
Tff7b72for Te6edf3entry Tff7b72in Te6edf3entriesTb4b4b4:
Te6edf3h Tff7b72= Te6edf3entryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4)
Tff7b72if Te6edf3hTb4b4b4:
Te6edf3seenTb4b4b4[Te6edf3hTb4b4b4] Tff7b72= Te6edf3entry
Tff7b72elseTb4b4b4:
Te6edf3no_hashTff7b72.Td2a8ffappendTb4b4b4(Te6edf3entryTb4b4b4)
Te6edf3unique_entries Tff7b72= Tffa657listTb4b4b4(Te6edf3seenTff7b72.Td2a8ffvaluesTb4b4b4(Tb4b4b4)Tb4b4b4) Tff7b72+ Te6edf3no_hash
Te6edf3imported Tff7b72= T79c0ff0
Te6edf3skipped Tff7b72= T79c0ff0
Tff7b72for Te6edf3entry Tff7b72in Te6edf3unique_entriesTb4b4b4:
Te6edf3dest_hash Tff7b72= Te6edf3entryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdestination_hashTa5d6ff"Tb4b4b4)
Te6edf3display_name Tff7b72= Te6edf3entryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffdisplay_nameTa5d6ff"Tb4b4b4, Ta5d6ff"Ta5d6ff"Tb4b4b4)
Te6edf3aspect Tff7b72= Te6edf3entryTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ffaspectTa5d6ff"Tb4b4b4)
Tff7b72if Tff7b72not Te6edf3dest_hash Tff7b72or Tff7b72not Te6edf3aspectTb4b4b4:
Te6edf3skipped Tff7b72+Tff7b72= T79c0ff1
Tff7b72continue
Tff7b72tryTb4b4b4:
Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffupsert_favouriteTb4b4b4(
Te6edf3dest_hashTb4b4b4,
Te6edf3display_nameTb4b4b4,
Te6edf3aspectTb4b4b4,
Tb4b4b4)
Te6edf3imported Tff7b72+Tff7b72= T79c0ff1
Tff7b72except Tf85149ExceptionTb4b4b4:
Te6edf3skipped Tff7b72+Tff7b72= T79c0ff1
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{
Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffFavourites import completeTa5d6ff"Tb4b4b4,
Ta5d6ff"Ta5d6ffimportedTa5d6ff"Tb4b4b4: Te6edf3importedTb4b4b4,
Ta5d6ff"Ta5d6ffskippedTa5d6ff"Tb4b4b4: Te6edf3skippedTb4b4b4,
Tb4b4b4}Tb4b4b4,
Tb4b4b4)
Tff7b72except Tf85149Exception Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6fffTa5d6ff"Ta5d6ffFailed to import favourites: Tffd700{Te6edf3eTffd700!s}Ta5d6ff"Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff500Tb4b4b4,
Tb4b4b4)
Tf0883e@routesTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/favourites/layoutTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8fffavourites_layout_getTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
Te6edf3layout Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffget_favourites_layoutTb4b4b4(Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6fflayoutTa5d6ff"Tb4b4b4: Te6edf3layoutTb4b4b4}Tb4b4b4)
Tf0883e@routesTff7b72.Td2a8ffputTb4b4b4(Ta5d6ff"Ta5d6ff/api/v1/favourites/layoutTa5d6ff"Tb4b4b4)
Tff7b72async Tff7b72def Td2a8fffavourites_layout_putTb4b4b4(Te6edf3requestTb4b4b4)Tb4b4b4:
Tff7b72from T7ee787meshchatxT7ee787.T7ee787srcT7ee787.T7ee787backendT7ee787.T7ee787favourites_layout Tff7b72import Te6edf3layout_payload_too_large
Te6edf3content_length Tff7b72= Te6edf3requestTff7b72.Td2a8ffcontent_length
Tff7b72if Te6edf3content_length Tff7b72is Tff7b72not Tff7b72None Tff7b72and Te6edf3layout_payload_too_largeTb4b4b4(
Te6edf3content_lengthTb4b4b4,
Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6fffavourites layout exceeds size limitTa5d6ff"Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff413Tb4b4b4,
Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3raw Tff7b72= Tff7b72await Te6edf3requestTff7b72.Td2a8ffreadTb4b4b4(Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffInvalid request bodyTa5d6ff"Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff400Tb4b4b4,
Tb4b4b4)
Tff7b72if Te6edf3layout_payload_too_largeTb4b4b4(Tffa657lenTb4b4b4(Te6edf3rawTb4b4b4)Tb4b4b4)Tb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6fffavourites layout exceeds size limitTa5d6ff"Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff413Tb4b4b4,
Tb4b4b4)
Tff7b72tryTb4b4b4:
Te6edf3data Tff7b72= Te6edf3jsonTff7b72.Td2a8ffloadsTb4b4b4(Te6edf3rawTff7b72.Td2a8ffdecodeTb4b4b4(Ta5d6ff"Ta5d6ffutf-8Ta5d6ff"Tb4b4b4)Tb4b4b4)
Tff7b72except Tf85149ExceptionTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(
Tb4b4b4{Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Ta5d6ff"Ta5d6ffInvalid JSON bodyTa5d6ff"Tb4b4b4}Tb4b4b4,
Te6edf3statusTff7b72=T79c0ff400Tb4b4b4,
Tb4b4b4)
Te6edf3layout Tff7b72= Te6edf3dataTff7b72.Td2a8ffgetTb4b4b4(Ta5d6ff"Ta5d6fflayoutTa5d6ff"Tb4b4b4) Tff7b72if Tffa657isinstanceTb4b4b4(Te6edf3dataTb4b4b4, Tffa657dictTb4b4b4) Tff7b72else Tff7b72None
Tff7b72tryTb4b4b4:
Te6edf3saved Tff7b72= Te6edf3appTff7b72.Td2a8ffdatabaseTff7b72.Td2a8ffannouncesTff7b72.Td2a8ffset_favourites_layoutTb4b4b4(Te6edf3layoutTb4b4b4)
Tff7b72except Tf85149ValueError Tff7b72as Te6edf3eTb4b4b4:
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6ffmessageTa5d6ff"Tb4b4b4: Tffa657strTb4b4b4(Te6edf3eTb4b4b4)Tb4b4b4}Tb4b4b4, Te6edf3statusTff7b72=T79c0ff400Tb4b4b4)
Tff7b72return Te6edf3webTff7b72.Td2a8ffjson_responseTb4b4b4(Tb4b4b4{Ta5d6ff"Ta5d6fflayoutTa5d6ff"Tb4b4b4: Te6edf3savedTb4b4b4}Tb4b4b4)
T8b949e# serve archived pages
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────